import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

import 'scrawl_size_slider_components.dart';

/// 画笔大小滑杆
class ScrawlSizeSlider extends StatefulWidget {
  ScrawlSizeSlider({
    Key key,
    this.value,
    this.onChanged,
    this.sections = 3,
  })  : assert(value != null && value <= sections),
        assert(sections != null),
        super(key: key);

  final int value;
  final ValueChanged<int> onChanged;

  /// 分区数量
  final int sections;

  @override
  _ScrawlSizeSliderState createState() => _ScrawlSizeSliderState();
}

class _ScrawlSizeSliderState extends State<ScrawlSizeSlider> {
  void _handleChanged(int value) {
    if (widget.onChanged == null) return;
    // 值处理

    widget.onChanged(value);
  }

  @override
  Widget build(BuildContext context) {
    return ScrawlSizeSliderRenderObjectWidget(
      value: widget.value,
      onChanged: _handleChanged,
      sections: widget.sections,
      state: this,
    );
  }
}

/// LeafRenderObjectWidget
class ScrawlSizeSliderRenderObjectWidget extends LeafRenderObjectWidget {
  ScrawlSizeSliderRenderObjectWidget({
    Key key,
    this.value,
    this.onChanged,
    this.sections,
    this.state,
  }) : super(key: key);

  final int value;
  final ValueChanged<int> onChanged;
  final int sections;
  final _ScrawlSizeSliderState state;

  @override
  RenderObject createRenderObject(BuildContext context) {
    return ScrawlSizeSliderRenderObject(
      value: value,
      sections: sections,
      onChanged: onChanged,
      state: state,
    );
  }

  @override
  void updateRenderObject(
    BuildContext context,
    ScrawlSizeSliderRenderObject renderObject,
  ) {
    renderObject
      ..value = value
      ..sections = sections
      ..onChanged = onChanged;
  }
}

class ScrawlSizeSliderRenderObject extends RenderBox {
  ScrawlSizeSliderRenderObject({
    int value,
    ValueChanged<int> onChanged,
    int sections,
    @required _ScrawlSizeSliderState state,
    this.onChangeStart,
    this.onChangeEnd,
  })  : assert(value != null),
        _value = value,
        _onChanged = onChanged,
        assert(sections != null),
        _sections = sections,
        assert(state != null),
        _state = state {
    final GestureArenaTeam team = GestureArenaTeam();
    _drag = HorizontalDragGestureRecognizer()
      ..team = team
      ..onStart = (DragStartDetails details) {
        _startInteraction(details.globalPosition);
      }
      ..onUpdate = _handleDragUpdate
      ..onEnd = (DragEndDetails details) {
        _endInteraction();
      }
      ..onCancel = () {
        _endInteraction();
      };
    _tap = TapGestureRecognizer()
      ..team = team
      ..onTapDown = (TapDownDetails details) {
        _startInteraction(details.globalPosition);
      }
      ..onTapUp = (TapUpDetails details) {
        _endInteraction();
      }
      ..onTapCancel = () {
        _endInteraction();
      };
  }

  int _value;
  int get value => _value;
  set value(int newValue) {
    if (newValue == _value) {
      return;
    }
    _value = newValue;
    // markNeedsSemanticsUpdate();
    markNeedsPaint();
  }

  ValueChanged<int> _onChanged;
  ValueChanged<int> get onChanged => _onChanged;
  set onChanged(ValueChanged<int> newValue) {
    if (newValue == _onChanged) {
      return;
    }
    _onChanged = newValue;
    markNeedsPaint();
  }

  ValueChanged<int> onChangeStart;
  ValueChanged<int> onChangeEnd;

  int _sections;
  int get sections => _sections;
  set sections(int newValue) {
    if (newValue == _sections) {
      return;
    }
    _sections = newValue;
    markNeedsPaint();
  }

  final _ScrawlSizeSliderState _state;
  bool get isInteractive => onChanged != null;

  /// 布局
  // This value is the touch target, 48, multiplied by 3.
  static const double _minPreferredTrackWidth = 144.0;
  Rect get trackRect => ScrawlSizeSliderStyle.trackRect(parentBox: this);
  Rect get valueRect => ScrawlSizeSliderStyle.valueRect(parentBox: this);

  HorizontalDragGestureRecognizer _drag;
  TapGestureRecognizer _tap;
  double _currentDragValue = 0.0;

  /// 手势处理
  void _startInteraction(Offset globalPosition) {
    if (isInteractive) {
      // We supply the *current* value as the start location, so that if we have
      // a tap, it consists of a call to onChangeStart with the previous value and
      // a call to onChangeEnd with the new value.
      if (onChangeStart != null) {
        onChangeStart(value);
      }
      _currentDragValue = _getValueFromGlobalPosition(globalPosition);
      onChanged(_discretize(_currentDragValue));
    }
  }

  void _endInteraction() {
    if (_state.mounted) {
      if (onChangeEnd != null) {
        onChangeEnd(_discretize(_currentDragValue));
      }
      _currentDragValue = 0.0;
    }
  }

  void _handleDragUpdate(DragUpdateDetails details) {
    if (!isInteractive) return;

    final double valueDelta = details.primaryDelta / valueRect.width;
    _currentDragValue += valueDelta;
    // print('currentDragValue: $_currentDragValue');

    onChanged(_discretize(_currentDragValue));
  }

  /// 工具
  // double [0, 1] -> int [0, 1, 2, 3]
  int _discretize(double value) {
    final int max = _sections;
    int result = (value * max).round().clamp(0, max) as int;
    return result;
  }

  double _getValueFromGlobalPosition(Offset globalPosition) {
    final double visualPosition =
        (globalToLocal(globalPosition).dx - valueRect.left) / valueRect.width;
    return visualPosition;
  }

  @override
  bool hitTestSelf(Offset position) => true;

  @override
  void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
    assert(debugHandleEvent(event, entry));
    if (event is PointerDownEvent && isInteractive) {
      // We need to add the drag first so that it has priority.
      _drag.addPointer(event);
      _tap.addPointer(event);
    }
  }

  // @override
  // double computeMinIntrinsicWidth(double height) => _minPreferredTrackWidth;

  // @override
  // double computeMaxIntrinsicWidth(double height) => _minPreferredTrackWidth;

  @override
  bool get sizedByParent => true;

  @override
  void performResize() {
    size = Size(
      constraints.hasBoundedWidth
          ? constraints.maxWidth
          : _minPreferredTrackWidth,
      constraints.maxHeight,
    );
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    final Rect valueRect = ScrawlSizeSliderStyle.valueRect(
      parentBox: this,
      offset: offset,
    );
    final thumbCenter = Offset(
      valueRect.left + value / sections * valueRect.width,
      valueRect.center.dy,
    );

    ScrawlSizeSliderComponents.drawTrack(
      context,
      offset,
      parentBox: this,
      thumbCenter: thumbCenter,
      sections: sections,
      value: value,
    );

    ScrawlSizeSliderComponents.drawThumb(
      context,
      thumbCenter,
      parentBox: this,
    );
  }
}
